//-----------------------------------------------------------------------------
//
// @file	Geometry.cpp
// @brief	Geometry file
// @author	Vinod Melapudi
// @date	20Feb2010
//
//-----------------------------------------------------------------------------

#include <list>

#include "Mesh.h"
#include "OpenGLRender.h"


// renderer initialized in main file
extern Renderer *g_renderer;


// Find Faces at given vertex
void Mesh::HalfEdgeRep::FindFacesFromVertex( uint vidx, vector<uint>& findices )
{
	// Get the 1st edge & its face
	uint start_edge_idx = vertices[ vidx ];
	Edge* curr_edge = &edges[ start_edge_idx ];

	// Iterate and keep getting the next face from the pair edges
	while( curr_edge->oe != -1 )
	{
		uint next_edge_idx = edges[ curr_edge->oe ].ne;
		curr_edge = &edges[ next_edge_idx ];
		findices.push_back( curr_edge->f );

		// If we are looping back to start of the eidx, then exit
		if ( next_edge_idx == start_edge_idx )
			return;
	}


	// If we dint loop around the vertex using pair edges, then get face on the other side of eidx
	curr_edge = &edges[ start_edge_idx ];
	while(1)
	{
		// Get the edge that ends in vidx
		curr_edge = &edges[ curr_edge->pe ];
		findices.push_back( curr_edge->f );

		if ( curr_edge->oe == -1 || curr_edge->oe == start_edge_idx )
			return;

		curr_edge = &edges[ curr_edge->oe ];
	}
}




Mesh::Mesh() :
	m_visible(true),
	m_render_mode(RENDER_MODE_DEFAULT),
	m_polygon_mode(POLYGON_MODE_TRIANGLE),
	m_half_edge_rep(NULL),
	m_render_method(RENDER_METHOD_VERTEX_BUFFER_OBJECT),
	m_vbo_position(-1),
	m_vbo_normal(-1),
	m_vbo_color(-1),
	m_vbo_texcoord(-1)
{
}

Mesh::~Mesh()
{
	Reset();
}


// Generate half edge representation from m_face_vtx_indices & m_vtx_positions
void Mesh::GenerateHalfEdgeRep()
{
	// If rep already exists, delete and create a new one
	if ( m_half_edge_rep )
		delete m_half_edge_rep;
	m_half_edge_rep = new HalfEdgeRep;


	// Create temp mapping to find pair edges
	typedef list<uint>		EdgeList;			// List of edges
	vector<EdgeList>		vertex_edge_list;	// List of edges ending at a vertex


	// Allocate memory
	m_half_edge_rep->faces.resize( GetNumFaces() );
	m_half_edge_rep->vertices.resize( m_vtx_positions.size() );
	vertex_edge_list.resize( m_vtx_positions.size() );

	// Edge memory
	const int max_edges = m_face_vtx_indices.size()  * 2;	// num_faces * sides * num_of_half_edges
	m_half_edge_rep->edges.reserve( max_edges );


	// Vertices per face
	uint num_vertices_per_face = GetNumVerticesPerFace();


	// Fill up m_half_edge_rep, for each face
	int curr_edge_idx = 0;
	for( uint fidx = 0; fidx < GetNumFaces(); fidx ++ )
	{
		uint fsidx = fidx * num_vertices_per_face;

		// Fill face info
		uint first_edge_idx = curr_edge_idx;
		uint last_edge_idx = curr_edge_idx + num_vertices_per_face-1;

		m_half_edge_rep->faces[fidx] = curr_edge_idx;

		// For each vertex in the face
		for( uint vidx = 0; vidx < num_vertices_per_face; vidx++ )
		{
			// Get 1st vertex
			uint v0 = m_face_vtx_indices[fsidx+vidx];

			//Get next cyclic vertex
			uint v1 = ( vidx < num_vertices_per_face-1 ) ? m_face_vtx_indices[fsidx+vidx+1] : m_face_vtx_indices[fsidx];

			// Set up vertex rep
			m_half_edge_rep->vertices[v0] = curr_edge_idx;

			// Setup the half_edge structure
			if ( first_edge_idx == curr_edge_idx )
				m_half_edge_rep->edges.push_back( HalfEdgeRep::Edge( fidx, v1, curr_edge_idx+1, last_edge_idx ) );
			else if ( last_edge_idx == curr_edge_idx )
				m_half_edge_rep->edges.push_back( HalfEdgeRep::Edge( fidx, v1, first_edge_idx, curr_edge_idx-1 ) );
			else
				m_half_edge_rep->edges.push_back( HalfEdgeRep::Edge( fidx, v1, curr_edge_idx+1, curr_edge_idx-1 ) );
			
			// Set the edge in temp vertex_edge mapping
			vertex_edge_list[v0].push_front( curr_edge_idx );

			curr_edge_idx++;
		}
	}


	// Hook up pair edges
	for( uint sv=0; sv < vertex_edge_list.size(); sv++ )
	{
		// Go through each edge starting(source) at vidx
		for( EdgeList::const_iterator sv_eitr = vertex_edge_list[sv].begin(); sv_eitr != vertex_edge_list[sv].end(); sv_eitr++ )
		{
			// Get destination vertex of edge
			HalfEdgeRep::Edge* edge1 = &m_half_edge_rep->edges[*sv_eitr];
			uint dv = edge1->v;

			// Go through all edges with sv as source vertex
			for( EdgeList::const_iterator dv_eitr = vertex_edge_list[dv].begin(); dv_eitr != vertex_edge_list[dv].end(); dv_eitr++ )
			{
				// See if any of these edges has sv as destination vertex
				HalfEdgeRep::Edge* edge2 = &m_half_edge_rep->edges[*dv_eitr];
				if ( sv == edge2->v )
				{
					// Found a pair

					// Hook edge1 & edge2 as pair edges
					edge1->oe = *dv_eitr;
					edge2->oe = *sv_eitr;
					edge2->unique_edge = false;

					// Erase this entry and break the loop
					vertex_edge_list[dv].erase(dv_eitr);
					break;
				}
			}
		}

		// Clear all edges, since we have found a pair for them
		vertex_edge_list[sv].clear();
	}
}


// Generate normals of vertices using face info
void Mesh::GenerateNormals( bool flip )
{
	// Generate half edge representation
	GenerateHalfEdgeRep();

	// Run through each face and get its normal
	vector<Vector3> face_normals;
	face_normals.resize( GetNumFaces() );

	uint num_vertices_per_face = GetNumVerticesPerFace();
	
	Vector3 n1Vec, n2Vec, nVec;
	uint fsidx = 0;
	for( uint fidx = 0; fidx < GetNumFaces(); fidx ++ )
	{
		uint v0 = m_face_vtx_indices[fsidx];
		uint v1 = m_face_vtx_indices[fsidx+1];
		uint v2 = m_face_vtx_indices[fsidx+2];
		fsidx += num_vertices_per_face;

		n1Vec = m_vtx_positions[v1] - m_vtx_positions[v0];
		n2Vec = m_vtx_positions[v2] - m_vtx_positions[v0];
		nVec = n1Vec % n2Vec;
		if ( flip )
			nVec = nVec * -1;
		face_normals[ fidx ] = nVec.normalize();
	}


	// For each vertex, get all the normals of the face instanced at vertex and normalize it
	m_vtx_normals.resize( m_vtx_positions.size() );
	Vector3	vtx_normal;
	vector<uint> findices;
	for( uint vidx = 0; vidx < m_vtx_positions.size(); vidx++ )
	{
		// Get all faces at vidx
		findices.clear();
		m_half_edge_rep->FindFacesFromVertex( vidx, findices );

		// Get all normals of faces around vidx
		vtx_normal.set(0,0,0);
		for( vector<uint>::const_iterator fitr = findices.begin(); fitr != findices.end(); fitr++ )
			vtx_normal += face_normals[*fitr];

		m_vtx_normals[vidx] = vtx_normal.normalize();
	}
}

// Render using opengl immediate mode
void Mesh::RenderInImmediateMode( uint max_faces_to_render )
{
	bool vtx_has_color = m_vtx_colors.size() > 0;
	bool vtx_has_normal = m_vtx_normals.size() > 0;
	bool vtx_has_texcoord = m_vtx_texcoords.size() > 0;


	// Rendering using intermediate mode
	glBegin( m_polygon_mode == POLYGON_MODE_TRIANGLE ? GL_TRIANGLES : GL_QUADS );

	uint face_vtx_idx = 0;
	for( uint i=0; i < max_faces_to_render*GetNumVerticesPerFace(); i++ )
	{
		if ( m_render_mode == RENDER_MODE_WIREFRAME && face_vtx_idx == 0 )
		{
			glBegin( GL_LINE_LOOP );
		}

		// GL calls
		uint idx = m_face_vtx_indices[i];
		if ( vtx_has_color )			glColor4f( m_vtx_colors[idx].r, m_vtx_colors[idx].b, m_vtx_colors[idx].b, m_vtx_colors[idx].a );
		if ( vtx_has_normal )			glNormal3f( m_vtx_normals[idx].x, m_vtx_normals[idx].y, m_vtx_normals[idx].z );
		if ( vtx_has_texcoord )			glTexCoord2f( m_vtx_texcoords[idx].x, m_vtx_texcoords[idx].y );
		glVertex3f( m_vtx_positions[idx].x, m_vtx_positions[idx].y, m_vtx_positions[idx].z );


		if ( m_render_mode == RENDER_MODE_WIREFRAME && face_vtx_idx == GetNumVerticesPerFace()-1 )
		{
			face_vtx_idx = 0;
			glEnd();
		}
		else
			face_vtx_idx++;
	}

	glEnd();
}


// Render using opengl vertex array mode
void Mesh::RenderUsingVertexArrays( uint max_faces_to_render )
{
	// Enable and set arrays
	glEnableClientState( GL_VERTEX_ARRAY );
	glVertexPointer( 3, GL_FLOAT, 0, &m_vtx_positions[0] );

	if ( m_vtx_normals.size() > 0 )
	{
		glEnableClientState( GL_NORMAL_ARRAY );
		glNormalPointer( GL_FLOAT, 0, &m_vtx_normals[0] );
	}

	if ( m_vtx_colors.size() > 0 )
	{
		glEnableClientState( GL_COLOR_ARRAY );
		glColorPointer( 4, GL_FLOAT, 0, &m_vtx_colors[0] );
	}

	if ( m_vtx_texcoords.size() > 0 )
	{
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, &m_vtx_texcoords[0] );
	}

	const uint num_vertices_per_face = GetNumVerticesPerFace();

	// Draw elements using indices
	if ( m_render_mode == RENDER_MODE_WIREFRAME )
	{
		for( uint i=0; i < GetNumFaces(); i++ )
			glDrawElements( GL_LINE_LOOP, num_vertices_per_face, GL_UNSIGNED_INT, &m_face_vtx_indices[i*num_vertices_per_face] );
	}
	else
	{
		glDrawElements( m_polygon_mode == POLYGON_MODE_TRIANGLE ? GL_TRIANGLES : GL_QUADS, max_faces_to_render*num_vertices_per_face, GL_UNSIGNED_INT, &m_face_vtx_indices[0] );
	}

	// Disable arrays
	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState( GL_NORMAL_ARRAY );
	glDisableClientState( GL_COLOR_ARRAY );
	glDisableClientState( GL_TEXTURE_COORD_ARRAY );
}
 

// Render using opengl vertex buffer object
void Mesh::RenderUsingVertexBufferObject( uint max_faces_to_render )
{
	// Vertices
	if ( m_vbo_position == -1 )
		m_vbo_position = g_renderer->LoadVBO( m_vtx_positions.size() * 3 * sizeof(float), (void*) &m_vtx_positions[0] );
	else
		g_renderer->BindVBO( m_vbo_position );
	glEnableClientState( GL_VERTEX_ARRAY );
	glVertexPointer( 3, GL_FLOAT, 0, 0 );

	// Normals
	if ( m_vtx_normals.size() > 0 )
	{
		if ( m_vbo_normal == -1 )
			m_vbo_normal = g_renderer->LoadVBO( m_vtx_normals.size() * 3 * sizeof(float), &m_vtx_normals[0] );
		else
			g_renderer->BindVBO( m_vbo_normal );
		glEnableClientState( GL_NORMAL_ARRAY );
		glNormalPointer( GL_FLOAT, 0, 0 );
	}

	// Colors
	if ( m_vtx_colors.size() > 0 )
	{
		if ( m_vbo_color == -1 )
			m_vbo_color = g_renderer->LoadVBO( m_vtx_colors.size() * 4 * sizeof(float), &m_vtx_colors[0] );
		else
			g_renderer->BindVBO( m_vbo_color );
		glEnableClientState( GL_COLOR_ARRAY );
		glColorPointer( 4, GL_FLOAT, 0, 0 );
	}

	// Tex coords
	if ( m_vtx_texcoords.size() > 0 )
	{
		if ( m_vbo_texcoord == -1 )
			m_vbo_texcoord = g_renderer->LoadVBO( m_vtx_texcoords.size() * 2 * sizeof(float), &m_vtx_texcoords[0] );
		else
			g_renderer->BindVBO( m_vbo_texcoord );
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, 0 );
	}

	const uint num_vertices_per_face = GetNumVerticesPerFace();

	// Draw elements using indices
	if ( m_render_mode == RENDER_MODE_WIREFRAME )
	{
		for( uint i=0; i < GetNumFaces(); i++ )
			glDrawElements( GL_LINE_LOOP, num_vertices_per_face, GL_UNSIGNED_INT, &m_face_vtx_indices[i*num_vertices_per_face] );
	}
	else
	{
		glDrawElements( m_polygon_mode == POLYGON_MODE_TRIANGLE ? GL_TRIANGLES : GL_QUADS, max_faces_to_render*num_vertices_per_face, GL_UNSIGNED_INT, &m_face_vtx_indices[0] );
	}

	// Disable arrays
	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState( GL_NORMAL_ARRAY );
	glDisableClientState( GL_COLOR_ARRAY );
	glDisableClientState( GL_TEXTURE_COORD_ARRAY );

	// Unbind
	g_renderer->BindVBO(0);
}


// Render mesh
void Mesh::Render( uint max_faces_to_render )
{
	if ( !m_visible || max_faces_to_render == 0 )
		return;

	if ( max_faces_to_render > GetNumFaces() )
		max_faces_to_render = GetNumFaces();

	if ( m_render_method == RENDER_METHOD_IMMEDIATE )
		RenderInImmediateMode( max_faces_to_render );					// Render in immediate mode
	else if ( m_render_method == RENDER_METHOD_VERTEX_ARRAYS )
		RenderUsingVertexArrays( max_faces_to_render );					// Render with vertex arrays
	else
		RenderUsingVertexBufferObject( max_faces_to_render );			// Render with VBO
}



// Reset the polygons
void Mesh::Reset()
{
	m_visible = true;
	m_vtx_positions.clear();
	m_vtx_normals.clear();
	m_vtx_texcoords.clear();
	m_vtx_colors.clear();

	m_face_vtx_indices.clear();

	// Delete hald edge rep
	if ( m_half_edge_rep )
		delete m_half_edge_rep;


	// Delete vertex buffer object ids
	g_renderer->UnloadVBO(&m_vbo_position);
	g_renderer->UnloadVBO(&m_vbo_normal);
	g_renderer->UnloadVBO(&m_vbo_color);
	g_renderer->UnloadVBO(&m_vbo_texcoord);
	m_vbo_position = m_vbo_normal = m_vbo_color = m_vbo_texcoord = -1;
}
